package com.pap.globalexception.exception;

import com.pap.base.constant.JWTConstants;
import com.pap.base.dto.jwt.JWTUserDTO;
import com.pap.base.exception.JWTException;
import com.pap.base.exception.PapBusinessException;
import com.pap.base.util.jackson.JacksonUtilss;
import com.pap.base.util.jwt.JWTTokenUtilss;
import com.pap.base.util.string.StringUtilss;
import com.pap.obj.vo.response.ResponseVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Map;
import java.util.Set;

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 定义要捕获的异常 可以多个 @ExceptionHandler({})
     *
     * @param request  request
     * @param e        exception
     * @param response response
     * @return 响应结果
     */
    @ExceptionHandler(Exception.class)
    public ResponseVO exceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
        logger.error("GlobalExceptionHandler.exceptionHandler: [{}][{}][{}][{}]", request.getRequestURL(), getLoginUserInfoFromRequest(request), e.getMessage(), getStackTraceElementMessage(e));
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        Exception exception = (Exception) e;
        return new ResponseVO("901", exception.getMessage());
    }

    /**
     * 捕获  RuntimeException 异常
     *
     * @param request  request
     * @param e        exception
     * @param response response
     * @return 响应结果
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseVO runtimeExceptionHandler(HttpServletRequest request, final RuntimeException e, HttpServletResponse response) {
        logger.error("GlobalExceptionHandler.runtimeExceptionHandler: [{}][{}][{}][{}]", request.getRequestURL(), getLoginUserInfoFromRequest(request), e.getMessage(), getStackTraceElementMessage(e));
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        RuntimeException exception = (RuntimeException) e;
        return new ResponseVO("902", exception.getMessage());
    }

    /**
     * 捕获 UndeclaredThrowableException 异常
     *
     * @param request  request
     * @param e        exception
     * @param response response
     * @return 响应结果
     */
    @ExceptionHandler(UndeclaredThrowableException.class)
    public ResponseVO undeclaredThrowableExceptionHandler(HttpServletRequest request, final UndeclaredThrowableException e, HttpServletResponse response) {
        logger.error("GlobalExceptionHandler.undeclaredThrowableExceptionHandler: [{}]", e.getCause() != null ? e.getCause().toString() : e.toString());
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        return new ResponseVO("903", e.getMessage());
    }


    /**
     * 捕获 RollbackException 异常
     *
     * @param request  request
     * @param e        exception
     * @param response response
     * @return 响应结果
     */
//    @ExceptionHandler(javax.persistence.RollbackException.class)
//    public ResponseEntity rollbackException(HttpServletRequest request, final javax.persistence.RollbackException e, HttpServletResponse response) {
//        logger.error("GlobalExceptionHandler.rollbackException: [{}]", e.getCause() != null ? e.getCause().toString() : e.toString());
//        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
//        return new ResponseEntity<>(HashMapProxy.simError(903, e.getMessage()), HttpStatus.OK);
//    }

    /**
     * hibernate 事务异常
     * @param ex
     */
//    @ExceptionHandler(org.hibernate.TransactionException.class)
//    public void handleTransactionException(org.hibernate.TransactionException ex) {
//        // 检查异常链中是否包含事务超时异常
//        if (isTransactionTimeoutException(ex)) {
//            // 处理事务超时异常的逻辑
//            System.out.println("捕获到事务超时异常：" + ex.getMessage());
//            // 这里可以进行相应的处理，比如记录日志或者触发其他操作
//        } else {
//            // 处理其他类型的事务异常
//            System.out.println("捕获到其他类型的事务异常：" + ex.getMessage());
//        }
//    }
//
//    private boolean isTransactionTimeoutException(Throwable ex) {
//        // 递归检查异常链中是否包含事务超时异常  形如 @Transactional(timeout = 5) 这种注解
//        while (ex != null) {
//            if (ex instanceof org.hibernate.TransactionException
//                    && "transaction timeout expired".equals(ex.getMessage())) {
//                return true;
//            }
//            ex = ex.getCause();
//        }
//        return false;
//    }

    /**
     * 捕获  JWTException 异常
     *
     * @param request  request
     * @param e        exception
     * @param response response
     * @return 响应结果
     */
    @ExceptionHandler(JWTException.class)
    public ResponseVO JWTExceptionHandler(HttpServletRequest request, final JWTException e, HttpServletResponse response) {
        logger.error("GlobalExceptionHandler.JWTExceptionHandler: [{}]", e.toString());
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        RuntimeException exception = (RuntimeException) e;
        return new ResponseVO("904", exception.getMessage());
    }

    @ExceptionHandler(PapBusinessException.class)
    public ResponseVO papBusinessExceptionHandler(HttpServletRequest request, final PapBusinessException e, HttpServletResponse response) {
        logger.error("GlobalExceptionHandler.PapBusinessException: [{}][{}]", e.toString(), getStackTraceElementMessage(e));
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        PapBusinessException exception = (PapBusinessException) e;
        String errorMsg = exception.getMessage();
        Map<String, Object> map = JacksonUtilss.jsonToMap(errorMsg);
        return new ResponseVO(map.get("value").toString(), map.get("desc").toString());
    }

    /**
     * 通用的接口映射异常处理方
     */
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
                                                             HttpStatus status, WebRequest request) {
        if (ex instanceof ConstraintViolationException) {
            // 验证信息失败
            ConstraintViolationException exs = (ConstraintViolationException) ex;

            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            StringBuilder sb = new StringBuilder("");
            for (ConstraintViolation<?> item : violations) {
                sb.append(item.getMessage()).append(", ");
            }
            return new ResponseEntity<>(new ResponseVO(status.value() + "", sb.toString()), status);
        }
        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException exception = (MethodArgumentNotValidException) ex;
            // 参数校验失败，这里有可能存在分组校验的情况，增加对应的处理.
            BindingResult bindingResult = exception.getBindingResult();
            StringBuilder sb = new StringBuilder("");
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                sb.append(fieldError.getField()).append("：").append(fieldError.getDefaultMessage()).append(", ");
            }

            logger.error("参数校验失败，方法：" + requestMethodName(exception) + "，参数：" + exception.getParameter()
                    + ",信息：" + sb.toString());
            return new ResponseEntity<>(new ResponseVO(status.value() + "", sb.toString()), status);
        }
        if (ex instanceof MethodArgumentTypeMismatchException) {
            MethodArgumentTypeMismatchException exception = (MethodArgumentTypeMismatchException) ex;
            logger.error("参数转换失败，方法：" + requestMethodName(exception) + "，参数：" + exception.getName()
                    + ",信息：" + exception.getLocalizedMessage());
            return new ResponseEntity<>(new ResponseVO(status.value() + "", "参数转换失败"), status);
        }
        if (ex instanceof HttpMessageNotReadableException) {
            // 比如请求需要一个json 对象， 但是实际的请求值并不是一个真正的 json
            HttpMessageNotReadableException exception = (HttpMessageNotReadableException) ex;
            logger.error("请求值数据不可读：" + exception.getMessage() + ",信息：" + exception.getLocalizedMessage());
            return new ResponseEntity<>(new ResponseVO(status.value() + "", "请求值数据不可读"), status);
        }
        if (ex instanceof HttpMessageNotWritableException) {
            // json 序列化失败，比如增加了格外的数据转换定义，但是在转换的过程中出现了异常
            HttpMessageNotWritableException exception = (HttpMessageNotWritableException) ex;
            logger.error("返回值数据不可写：" + exception.getMessage() + ",信息：" + exception.getLocalizedMessage());
            return new ResponseEntity<>(new ResponseVO(status.value() + "", "返回值数据不可写"), status);
        }
        if (ex instanceof HttpMediaTypeNotAcceptableException) {
            HttpMediaTypeNotAcceptableException exception = (HttpMediaTypeNotAcceptableException) ex;
            logger.error("不支持的媒体类型：" + exception.getMessage() + ",信息：" + exception.getLocalizedMessage());
            return new ResponseEntity<>(new ResponseVO(status.value() + "", "不支持的媒体类型"), status);
        }
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            HttpRequestMethodNotSupportedException exception = (HttpRequestMethodNotSupportedException) ex;

            logger.error("请求方法不支持：" + exception.getMessage() + ",信息：" + exception.getLocalizedMessage() + ",支持方法: "
                    + setToString(exception.getSupportedHttpMethods(), StringUtilss.COMMA));
            return new ResponseEntity<>(new ResponseVO(status.value() + "", "请求方法不支持,支持: "
                    + setToString(exception.getSupportedHttpMethods(), StringUtilss.COMMA)), status);
        }
        if (ex instanceof MissingServletRequestParameterException) {
            MissingServletRequestParameterException exception = (MissingServletRequestParameterException) ex;

            logger.error("参数缺失：" + exception.getMessage() + ",信息：" + exception.getLocalizedMessage());
            return new ResponseEntity<>(new ResponseVO(status.value() + "", "参数缺失: "
                    + exception.getLocalizedMessage()), status);
        }
        return new ResponseEntity<>(new ResponseVO(status.value() + "", "请求异常!"), status);
    }


    public static String setToString(Set<HttpMethod> stringSet, String splitOperator) {
        if (stringSet == null) {
            return null;
        }
        StringBuilder result = new StringBuilder();
        boolean flag = false;
        for (HttpMethod httpMethod : stringSet) {
            if (flag) {
                result.append(splitOperator);
            } else {
                flag = true;
            }
            result.append(httpMethod.name());
        }
        return result.toString();
    }

    public static String requestMethodName (MethodArgumentNotValidException exception) {
        String name = "未知";
        if(exception != null && exception.getParameter() != null && exception.getParameter().getMethod() != null) {
            name = exception.getParameter().getMethod().getName();
        }
        return name;
    }

    public static String requestMethodName (MethodArgumentTypeMismatchException exception) {
        String name = "未知";
        if(exception != null && exception.getParameter() != null && exception.getParameter().getMethod() != null) {
            name = exception.getParameter().getMethod().getName();
        }
        return name;
    }

    /**
     * @param ex
     * @Description 获取 StackTraceElementMessage 信息，此处规则是根据包名和 类名规则处理,并且限定为三次，符合太多也不行
     */
    private String getStackTraceElementMessage(Throwable ex) {
        StringBuilder sb = new StringBuilder();

        StackTraceElement[] stackTraceElements = ex.getStackTrace();
        sb.append(ex.fillInStackTrace() + "  ");
        if (ex.getCause() != null) {
            sb.append(ex.getCause().getMessage() + "  ");
        }
        for (StackTraceElement stackTraceElement : stackTraceElements) {
            String className = stackTraceElement.getClassName();
            if (className.contains("com.pap")) {
                sb.append(stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + "(" + stackTraceElement.getFileName() + ":"
                        + stackTraceElement.getLineNumber() + ")   ");
            }
        }
        return sb.toString();
    }

    /**
     * 从 header 中获取 jwt 并进行解析，返回当前登录用户
     *
     * @param request
     * @return
     */
    private String getLoginUserInfoFromRequest(HttpServletRequest request) {
        String returnStr = "null";
        String loginUserHeaderToken = request.getHeader("papToken");
        if (StringUtilss.isNotEmpty(loginUserHeaderToken) && loginUserHeaderToken.startsWith("Bearer ")) {
            try {
                JWTUserDTO jwtUserDTO = JWTTokenUtilss.parse(loginUserHeaderToken.substring(7), JWTConstants.SECRET);
                returnStr = jwtUserDTO.getUserId() + ":" + jwtUserDTO.getTenantId();
            } catch (JWTException e) {
            } catch (Exception e) {
            }
        }
        return returnStr;
    }

    /**
     * 友好打印异常堆栈信息
     * @param throwable
     * @param maxDepth
     * @return
     */
    public static String printRecursiveStackTrace(Throwable throwable, int maxDepth) {
        StringBuilder sBuilder = new StringBuilder();
        return printRecursiveStackTrace(throwable, maxDepth, 0, sBuilder).toString();
    }

    private static StringBuilder printRecursiveStackTrace(Throwable throwable, int maxDepth, int currentDepth, StringBuilder sBuilder) {
        if (currentDepth >= maxDepth) {
            return sBuilder;
        }

        if (throwable.getCause() != null) {
            sBuilder.append(throwable.getCause().getMessage());
            return printRecursiveStackTrace(throwable.getCause(), maxDepth, currentDepth + 1, sBuilder);
        } else {
            return sBuilder;
        }
    }

}
